package cn.csg.iotplatform.module.system.service.permission;

import cn.csg.iotplatform.framework.common.enums.CommonStatusEnum;
import cn.csg.iotplatform.framework.common.pojo.PageResult;
import cn.csg.iotplatform.framework.common.util.collection.CollectionUtils;
import cn.csg.iotplatform.framework.common.util.common.ToolUtil;
import cn.csg.iotplatform.framework.common.util.json.JsonUtils;
import cn.csg.iotplatform.framework.common.util.object.BeanUtils;
import cn.csg.iotplatform.framework.datapermission.core.annotation.DataPermission;
import cn.csg.iotplatform.framework.web.core.util.WebFrameworkUtils;
import cn.csg.iotplatform.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.csg.iotplatform.module.system.api.permission.dto.UserZoneDomainDto;
import cn.csg.iotplatform.module.system.controller.admin.permission.vo.permission.*;
import cn.csg.iotplatform.module.system.controller.admin.permission.vo.role.UserRolesReqDto;
import cn.csg.iotplatform.module.system.controller.admin.permission.vo.role.UserRolesRespVo;
import cn.csg.iotplatform.module.system.controller.admin.user.vo.user.*;
import cn.csg.iotplatform.module.system.convert.user.UserConvert;
import cn.csg.iotplatform.module.system.dal.dataobject.dept.DeptDO;
import cn.csg.iotplatform.module.system.dal.dataobject.dict.DictDataDO;
import cn.csg.iotplatform.module.system.dal.dataobject.permission.RoleDO;
import cn.csg.iotplatform.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.csg.iotplatform.module.system.dal.dataobject.permission.UserRoleDO;
import cn.csg.iotplatform.module.system.dal.dataobject.user.AdminUserDO;
import cn.csg.iotplatform.module.system.dal.mysql.permission.RoleMapper;
import cn.csg.iotplatform.module.system.dal.mysql.permission.RoleMenuMapper;
import cn.csg.iotplatform.module.system.dal.mysql.permission.UserRoleMapper;
import cn.csg.iotplatform.module.system.dal.mysql.user.AdminUserMapper;
import cn.csg.iotplatform.module.system.dal.mysql.userzone.UserZoneMapper;
import cn.csg.iotplatform.module.system.dal.redis.RedisKeyConstants;
import cn.csg.iotplatform.module.system.enums.permission.DataScopeEnum;
import cn.csg.iotplatform.module.system.enums.user.UserIdEnum;
import cn.csg.iotplatform.module.system.service.dept.DeptService;
import cn.csg.iotplatform.module.system.service.dict.DictDataService;
import cn.csg.iotplatform.module.system.service.user.AdminUserService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static cn.csg.iotplatform.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.csg.iotplatform.framework.common.util.collection.CollectionUtils.*;
import static cn.csg.iotplatform.framework.common.util.json.JsonUtils.toJsonString;
import static cn.csg.iotplatform.module.system.enums.ErrorCodeConstants.EXIST_USER_NO_PERMISSION_ROLE;
import static cn.csg.iotplatform.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;

/**
 * 权限 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    @Resource
    private DictDataService dictDataService;

    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;
    @Resource
    private DeptService deptService;
    @Resource
    private AdminUserService userService;

    @Resource
    private AdminUserMapper userMapper;
    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private UserRoleMapper userRoleMapper;
    @Resource
    private RoleMapper roleMapper;
    @Resource
    private UserZoneMapper userZoneMapper;


    @Override
    public boolean hasAnyPermissions(Long userId, String... permissions) {
        //如果用户是Super Admin(超级管理员)，直接返回true
        if (userId == UserIdEnum.SUPERADMIN.getUserId()){
            return true;
        }

        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(permissions)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roles)) {
            return false;
        }

        // 情况一：遍历判断每个权限，如果有一满足，说明有权限
        for (String permission : permissions) {
            if (hasAnyPermission(roles, permission)) {
                return true;
            }
        }

        // 情况二：如果是超管，也说明有权限
        return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
    }

    /**
     * 判断指定角色，是否拥有该 permission 权限
     *
     * @param roles      指定角色数组
     * @param permission 权限标识
     * @return 是否拥有
     */
    private boolean hasAnyPermission(List<RoleDO> roles, String permission) {
        List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);
        // 采用严格模式，如果权限找不到对应的 Menu 的话，也认为没有权限
        if (CollUtil.isEmpty(menuIds)) {
            return false;
        }

        // 判断是否有权限
        Set<Long> roleIds = convertSet(roles, RoleDO::getId);
        for (Long menuId : menuIds) {
            // 获得拥有该菜单的角色编号集合
            Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
            // 如果有交集，说明有权限
            if (CollUtil.containsAny(menuRoleIds, roleIds)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasAnyRoles(Long userId, String... roles) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(roles)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roleList)) {
            return false;
        }

        // 判断是否有角色
        Set<String> userRoles = convertSet(roleList, RoleDO::getCode);
        return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
    }

    // ========== 角色-菜单的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，主要一次更新涉及到的 menuIds 较多，反倒批量会更快
    public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
        // 获得角色拥有菜单编号
        Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
        // 计算新增和删除的菜单编号
        Set<Long> menuIdList = CollUtil.emptyIfNull(menuIds);
        Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (CollUtil.isNotEmpty(createMenuIds)) {
            roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
                RoleMenuDO entity = new RoleMenuDO();
                entity.setRoleId(roleId);
                entity.setMenuId(menuId);
                return entity;
            }));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict = {
            @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
                    allEntries = true), // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 menu 缓存们
            @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST,
                    allEntries = true) // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 user 缓存们
    })
    public void processRoleDeleted(Long roleId) {
        // 标记删除 UserRole
        userRoleMapper.deleteListByRoleId(roleId);
        // 标记删除 RoleMenu
        roleMenuMapper.deleteListByRoleId(roleId);
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public void processMenuDeleted(Long menuId) {
        roleMenuMapper.deleteListByMenuId(menuId);
    }

    @Override
    public Set<Long> getRoleMenuListByRoleId(Long roleId) {
        if (roleId == null){
            return Collections.emptySet();
        }else {
            return convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
        }
    }

    @Override
    public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
        return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
    }

    // ========== 用户-角色的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void assignUserRole(Long userId, Set<Long> roleIds) {
        // 获得角色拥有角色编号
        Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
                UserRoleDO::getRoleId);
        // 计算新增和删除的角色编号
        Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds);
        Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createRoleIds)) {
            userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                UserRoleDO entity = new UserRoleDO();
                entity.setUserId(userId);
                entity.setRoleId(roleId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
            userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
        }
    }

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void assignUserRoleNewVersion(Long userId, Set<Long> roleIds, Long editUserId) {
        //判断当前登入用户修改的用户角色是不是越权了
        if (editUserId != 1L){
            AdminUserDO editAdminUserDO = userMapper.selectById(editUserId);
            if (editAdminUserDO == null){
                throw exception(USER_NOT_EXISTS);
            }
            LambdaQueryWrapper<RoleDO> editUserCreateRoleDOLambdaQueryWrapper = new LambdaQueryWrapper<RoleDO>()
                    .eq(RoleDO::getCreator, editUserId)
                    .eq(RoleDO::getStatus, 0);
            List<RoleDO> editUserCreateRoleDOS = roleMapper.selectList(editUserCreateRoleDOLambdaQueryWrapper);
            Map<Long, Integer> editUserRoleIdMap = editUserCreateRoleDOS.stream().collect(Collectors.toMap(RoleDO::getId, v -> 1));

            List<UserRoleDO> editUserOwnerRoleDOS = userRoleMapper.selectListByUserId(editUserId);
            editUserRoleIdMap.putAll(editUserOwnerRoleDOS.stream().collect(Collectors.toMap(UserRoleDO::getRoleId, v -> 1)));

            boolean allMatch = roleIds.stream().allMatch(v -> editUserRoleIdMap.containsKey(v));
            if (!allMatch){
                throw exception(EXIST_USER_NO_PERMISSION_ROLE);
            }
        }

        // 获得角色拥有角色编号
        Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
                UserRoleDO::getRoleId);
        // 计算新增和删除的角色编号
        Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds);
        Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createRoleIds)) {
            userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                UserRoleDO entity = new UserRoleDO();
                entity.setUserId(userId);
                entity.setRoleId(roleId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
            userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
        }
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void processUserDeleted(Long userId) {
        userRoleMapper.deleteListByUserId(userId);
    }

    @Override
    public Set<Long> getUserRoleIdListByUserId(Long userId) {
        return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);
    }

    @Override
    public Set<Long> getUserRoleIdListByUserIds(Set<Long> userIds) {
        return convertSet(userRoleMapper.selectListByUserIds(userIds), UserRoleDO::getRoleId);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
        return getUserRoleIdListByUserId(userId);
    }

    @Override
    public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {
        return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId);
    }

    /**
     * 获得用户拥有的角色，并且这些角色是开启状态的
     *
     * @param userId 用户编号
     * @return 用户拥有的角色
     */
    @VisibleForTesting
    List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {
        // 获得用户拥有的角色编号
        Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
        // 获得角色数组，并移除被禁用的
        List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
        return roles;
    }

    // ========== 用户-部门的相关方法  ==========

    @Override
    public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
        roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
    }

    @Override
    @DataPermission(enable = false) // 关闭数据权限，不然就会出现递归获取数据权限的问题
    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
        // 获得用户的角色
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);

        // 如果角色为空，则只能查看自己
        DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
        if (CollUtil.isEmpty(roles)) {
            result.setSelf(true);
            return result;
        }

        // 获得用户的部门编号的缓存，通过 Guava 的 Suppliers 惰性求值，即有且仅有第一次发起 DB 的查询
        Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
        // 遍历每个角色，计算
        for (RoleDO role : roles) {
            // 为空时，跳过
            if (role.getDataScope() == null) {
                continue;
            }
            // 情况一，ALL
            if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {
                result.setAll(true);
//              continue;
                break;
            }
            // 情况二，DEPT_CUSTOM
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {
                CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
                // 自定义可见部门时，保证可以看到自己所在的部门。否则，一些场景下可能会有问题。
                // 例如说，登录时，基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况三，DEPT_ONLY
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况四，DEPT_DEPT_AND_CHILD
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
                CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
                // 添加本身部门编号
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况五，SELF
            if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
                result.setSelf(true);
                continue;
            }
            // 未知情况，error log 即可
            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
        }
        return result;
    }

    @Override
    public PageResult<UserSimpleRespVO> getRoleUserListByRoleId(RoleUserListReqDto dto) {
        //1.关联数据查询
        PageResult<UserRoleDO> userRoleDOPageResult = this.userRoleMapper.getRoleUserListByRoleId(dto);
        List<Long> userIds = convertList(userRoleDOPageResult.getList(), UserRoleDO::getUserId);
        if (ToolUtil.isEmpty(userIds)) {
            return new PageResult<>(0L);
        }
        dto.setUserIds(userIds);
        //List<AdminUserDO> userList = this.userMapper.selectByUserIds(userIds);
        List<AdminUserDO> userList = this.userMapper.getRoleUserListByRoleId(dto);
        Map<Long, DeptDO> deptMap = this.deptService.getDeptMap(convertList(userList, AdminUserDO::getDeptId));
        //2.数据拼接返回
        List<UserSimpleRespVO> userSimpleRespVOS = UserConvert.INSTANCE.convertRoleUserList(userList, deptMap);
        return new PageResult<>(userSimpleRespVOS, userRoleDOPageResult.getTotal());
    }

    @Override
    public PageResult<UserRolesRespVo> pageUserRoles(UserRolesReqDto dto) {
        //1.获取登录用户id
        Long loginUserId = WebFrameworkUtils.getLoginUserId();
        dto.setLoginUserId(loginUserId); //设置当前用户id
        AdminUserDO user = this.userService.getUser(loginUserId);
        dto.setDeptId(user.getDeptId()); //设置部门id
        //2.查询角色列表
        List<UserRolesRespVo> list = this.roleMapper.queryUserRolesPage(dto);
        Long total = this.roleMapper.queryUserRolesTotal(dto);
        //数据拼接(创建人信息)
        Set<Long> creators = CollectionUtils.convertSet(list, UserRolesRespVo::getCreator);
        UserPageReqVO userPageReqVO = new UserPageReqVO();
        userPageReqVO.setIds(creators);
        PageResult<UserRespVO> userPage = this.userService.queryUserPage(userPageReqVO);

        Map<Long, UserRespVO> longAdminUserDOMap = convertMap(userPage.getList(), UserRespVO::getId);

        List<UserRolesRespVo> voList = CollectionUtils.convertList(list, role -> convertUserRolesCreators(role, longAdminUserDOMap.get(role.getCreator())));

        //3.封装返回结果
        return ToolUtil.setPage(total, voList);
    }

    @Override
    public PageResult<UserRolesRespVo> pageUserRolesNewVersion(UserRolesReqDto dto) {
        //1.获取登录用户id
        Long loginUserId = WebFrameworkUtils.getLoginUserId();
        AdminUserDO user = userService.getUser(loginUserId);
        if (user == null){
            throw exception(USER_NOT_EXISTS);
        }

        Page<UserRolesRespVo> queyrPage = new Page<UserRolesRespVo>(dto.getPageNo(), dto.getPageSize());
        IPage<UserRolesRespVo> userRolesRespVoIPage = roleMapper.queryUserOwnerConfigRolePage(queyrPage, dto.getUserId(), loginUserId, dto.getDescription(),dto.getTableNum());

        PageResult<UserRolesRespVo> pageResult = new PageResult<UserRolesRespVo>();
        pageResult.setList(userRolesRespVoIPage.getRecords());
        pageResult.setTotal(userRolesRespVoIPage.getTotal());

        return pageResult;
    }

    @Override
    public UserZoneDomainVo queryUserZoneDomains(Long userId) {
        //1.获取字典值(领域,分区)
        List<DictDataDO> cmDomains = this.dictDataService.getDictDataList(0, "CM_DOMAIN");
        List<DictDataDO> cmZones = this.dictDataService.getDictDataList(0, "CM_ZONE");
        //2.授权分区查询(针对userId用户)
        Set<String> zones = userZoneMapper.queryUsersZones(Collections.singleton(userId));
        //3.授权领域查询(针对userId用户)
        Set<String> domains = userZoneMapper.queryUsersDomains(Collections.singleton(userId));

        //4.数据整合
        List<PermissionExtend> zoneExtends = new ArrayList<>();
        List<PermissionExtend> domainExtends = new ArrayList<>();
        cmZones.forEach(zoneDo -> {
            if (zones.contains(zoneDo.getValue())) {
                zoneExtends.add(new PermissionExtend(zoneDo.getValue(), zoneDo.getLabel(), true));
            } else {
                zoneExtends.add(new PermissionExtend(zoneDo.getValue(), zoneDo.getLabel(), false));
            }
        });
        cmDomains.forEach(domainDo -> {
            if (domains.contains(domainDo.getValue())) {
                domainExtends.add(new PermissionExtend(domainDo.getValue(), domainDo.getLabel(), true));
            } else {
                domainExtends.add(new PermissionExtend(domainDo.getValue(), domainDo.getLabel(), false));
            }
        });
        UserZoneDomainVo userZoneDomainVo = new UserZoneDomainVo();
        userZoneDomainVo.getZones().addAll(zoneExtends);
        userZoneDomainVo.getDomains().addAll(domainExtends);

        return userZoneDomainVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignUserZoneDomains(UserZoneDomainDto dto) {
        Long userId = dto.getUserId();
        //移除缓存
        removeUserZoneDomainsCache(userId);
        //1.更新用户分区授权信息(设备权限)
        Set<String> dbZones = userZoneMapper.queryUsersZones(Collections.singleton(userId));
        Set<String> zones = CollUtil.emptyIfNull(dto.getZones());
        this.updateUsersZones(Collections.singleton(userId), dbZones, zones);
        //2.更新用户领域授权信息(设备权限)
        Set<String> dbDomains = userZoneMapper.queryUsersDomains(Collections.singleton(userId));
        Set<String> domains = CollUtil.emptyIfNull(dto.getDomains());
        this.updateUsersDomains(Collections.singleton(userId), dbDomains, domains);
    }


    @Override
    @Cacheable(cacheNames = RedisKeyConstants.USER_CACHE_ZONE_DOMAIN, key = "#userId")
    public UserZoneAndDomainDto getCacheUserPermission(Long userId) {
        List<String> zones = this.userZoneMapper.getZonesByUseId(userId);
        List<String> domains = this.userZoneMapper.getDomainsByUseId(userId);
        UserZoneAndDomainDto userZoneAndDomainDto = new UserZoneAndDomainDto();
        userZoneAndDomainDto.setUserId(userId);
        userZoneAndDomainDto.setZones(zones);
        userZoneAndDomainDto.setDomains(domains);
        return userZoneAndDomainDto;
    }

    @CacheEvict(cacheNames = RedisKeyConstants.USER_CACHE_ZONE_DOMAIN, key = "#userId")
    public void removeUserZoneDomainsCache(Long userId) {

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void copyUserRoles(CopyUsersRolesDto dto) {
        //1.查询源头用户的数据权限关系
        Set<Long> userIds = Collections.singleton(dto.getSourceUserId());
        //移除缓存
        for (Long userId : userIds) {
            removeUserZoneDomainsCache(userId);
        }
        //领域权限
        Set<String> dbDomains = userZoneMapper.queryUsersDomains(userIds);
        //分区权限
        Set<String> dbZones = userZoneMapper.queryUsersZones(userIds);
        //项目权限
        Set<String> dbProjects = userZoneMapper.queryUsersProjects(userIds);
        //单位权限
        Set<Long> dbDepts = userZoneMapper.queryUsersDepts(userIds);

        //2.更新目标用户的数据权限关系
        //领域
        Set<String> domains = userZoneMapper.queryUsersDomains(dto.getTargetUserIds());
        this.updateUsersDomains(dto.getTargetUserIds(), domains, dbDomains);
        //分区
        Set<String> zones = userZoneMapper.queryUsersZones(dto.getTargetUserIds());
        this.updateUsersZones(dto.getTargetUserIds(), zones, dbZones);
        //项目
        Set<String> projects = this.userZoneMapper.queryUsersProjects(dto.getTargetUserIds());
        this.updateUserProjects(dto.getTargetUserIds(), projects, dbProjects);
        //单位
        Set<Long> depts = userZoneMapper.queryUsersDepts(dto.getTargetUserIds());
        this.updateUserDepts(dto.getTargetUserIds(), depts, dbDepts);

    }

    @Override
    public PageResult<PageCopyUserRoleVo> pageCopyUserRoles(PageCopyUserRoleDto dto) {
        //1.查询待选用户数据
        List<PageCopyUserRoleDo> list = this.userZoneMapper.queryCopyUserRoleList(dto);
        Long total = this.userZoneMapper.queryCopyUserRoleTotal(dto);
        //2.翻译部门数据
        Set<Long> deptIds = list.stream()
                .flatMap(deptDO -> deptDO.getRelationDeptIds().stream())
                .collect(Collectors.toSet());
        List<DeptDO> deptList = this.deptService.getDeptList(deptIds);
        Map<Long, String> collect = deptList.stream().collect(Collectors.toMap(DeptDO::getId, DeptDO::getName));

        //数据拼接
        List<PageCopyUserRoleVo> pageCopyUserRoleVos = BeanUtils.toBean(list, PageCopyUserRoleVo.class);
        pageCopyUserRoleVos.forEach(deptVo -> {
            if (ToolUtil.isNotEmpty(deptVo.getRelationDeptIds())) {
                StringBuilder sb = new StringBuilder();
                deptVo.getRelationDeptIds().forEach(deptId -> {
                    if (collect.containsKey(deptId)) {
                        sb.append(collect.get(deptId));
                        sb.append(",");
                    }
                });
                deptVo.setRelationDeptNames(sb.length() > 0? sb.substring(0, sb.length() - 1) : "");
            }
        });

        return new PageResult<>(pageCopyUserRoleVos, total);
    }


    // -------------------------------------------------------------------------------------------------------------------------

    /**
     * 更新用户领域关系
     *
     * @param userIds   目前用户id集合
     * @param dbDomains 数据库关系list
     * @param domains   新关系list
     */
    private void updateUsersDomains(Set<Long> userIds, Set<String> dbDomains, Set<String> domains) {
        log.info("用户：{} 更新领域设备关系 >>> 现有关系：{},更新关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(dbDomains), JsonUtils.toJsonString(domains));
        //1.计算新增和删除的用户领域设备授权关系
        Collection<String> createDomains = CollUtil.subtract(domains, dbDomains);
        Collection<String> deleteDomains = CollUtil.subtract(dbDomains, domains);
        //2.执行新增和删除
        log.info("用户：{} 新增领域设备关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(createDomains));
        if (!CollectionUtil.isEmpty(createDomains)) {
            List<UserDomainDo> doList = new ArrayList<>();
            userIds.forEach(userId -> {
                createDomains.forEach(domain -> {
                    UserDomainDo userDomainDo=new UserDomainDo();
                    userDomainDo.setDomain(domain);
                    userDomainDo.setUserId(userId);
                    doList.add(userDomainDo);
                });
            });

            this.insertUserDomainBatch(doList);
        }
        log.info("用户：{} 删除领域设备关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(deleteDomains));
        if (!CollectionUtil.isEmpty(deleteDomains)) {
            this.deleteByUserIdAndDomains(userIds, deleteDomains);
        }
    }


    /**
     * 更新用户分区关系
     *
     * @param userIds 用户id
     * @param dbZones 数据库关系list
     * @param zones   新关系list
     */
    private void updateUsersZones(Set<Long> userIds, Set<String> dbZones, Set<String> zones) {
        log.info("用户：{} 更新分区设备关系 >>> 现有关系：{},更新关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(dbZones), JsonUtils.toJsonString(zones));
        //1.计算新增和删除的用户领域设备授权关系
        Collection<String> createZones = CollUtil.subtract(zones, dbZones);
        Collection<String> deleteZones = CollUtil.subtract(dbZones, zones);
        //2.执行新增和删除
        log.info("用户：{} 新增分区设备关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(createZones));
        if (!CollectionUtil.isEmpty(createZones)) {
            List<UserZoneDo> doList = new ArrayList<>();
            userIds.forEach(userId -> {
                createZones.forEach(zone -> {
                    UserZoneDo userZoneDo=new UserZoneDo();
                    userZoneDo.setZone(zone);
                    userZoneDo.setUserId(userId);
                    doList.add(userZoneDo);
                });
            });
            this.insertUserZoneBatch(doList);
        }
        log.info("用户：{} 删除分区设备关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(deleteZones));
        if (!CollectionUtil.isEmpty(deleteZones)) {
            this.deleteByUserIdAndZones(userIds, deleteZones);
        }

    }

    /**
     * 更新用户单位关系
     *
     * @param userIds 用户id
     * @param dbDepts 数据库关系list
     * @param depts   新关系list
     */
    private void updateUserDepts(Set<Long> userIds, Set<Long> dbDepts, Set<Long> depts) {
        log.info("用户：{} 更新单位设备关系 >>> 现有关系：{},更新关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(dbDepts), JsonUtils.toJsonString(depts));
        //1.计算新增和删除的用户单位设备授权关系
        Collection<Long> createDepts = CollUtil.subtract(depts, dbDepts);
        Collection<Long> deleteDepts = CollUtil.subtract(dbDepts, depts);
        //2.执行新增和删除
        log.info("用户：{} 新增单位设备关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(createDepts));
        if (!CollectionUtil.isEmpty(createDepts)) {
            this.insertUserDeptBatch(convertList(userIds,
                    userId -> new UserDeptDo().setUserId(userId).setDeptId((Long) createDepts.toArray()[0])));
        }
        log.info("用户：{} 删除单位设备关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(deleteDepts));
        if (!CollectionUtil.isEmpty(deleteDepts)) {
            this.deleteByUserIdAndDepts(userIds, deleteDepts);
        }

    }

    /**
     * 更新用户项目关系
     *
     * @param userIds    用户id
     * @param dbProjects 数据库关系list
     * @param projects   新关系list
     */
    private void updateUserProjects(Set<Long> userIds, Set<String> dbProjects, Set<String> projects) {
        log.info("用户：{} 更新项目设备关系 >>> 现有关系：{},更新关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(dbProjects), JsonUtils.toJsonString(projects));
        //1.计算新增和删除的用户项目设备授权关系
        Collection<String> createProjects = CollUtil.subtract(projects, dbProjects);
        Collection<String> deleteProjects = CollUtil.subtract(dbProjects, projects);
        //2.执行新增和删除
        log.info("用户：{} 新增项目设备关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(createProjects));
        if (!CollectionUtil.isEmpty(createProjects)) {
            this.insertUserProjectBatch(convertList(userIds,
                    userId -> new UserProjectDo().setUserId(userId).setProjectId((String) createProjects.toArray()[0])));
        }
        log.info("用户：{} 删除项目设备关系：{}", JsonUtils.toJsonString(userIds), JsonUtils.toJsonString(deleteProjects));
        if (!CollectionUtil.isEmpty(deleteProjects)) {
            this.deleteByUserIdAndProjects(userIds, deleteProjects);
        }
    }

    /**
     * 批量删除用户领域关系
     *
     * @param userIds       用户id
     * @param deleteDomains 执行list
     */
    private void deleteByUserIdAndDomains(Collection<Long> userIds, Collection<String> deleteDomains) {
        this.userZoneMapper.deleteByUserIdAndDomains(userIds, deleteDomains);
    }

    /**
     * 批量新增用户领域关系
     *
     * @param doList 执行list
     */
    private void insertUserDomainBatch(List<UserDomainDo> doList) {
        Long loginUserId = WebFrameworkUtils.getLoginUserId();
        this.userZoneMapper.insertUserDomainBatch(loginUserId, LocalDateTime.now(), doList);
    }

    /**
     * 批量删除用户分区关系
     *
     * @param userIds     用户id
     * @param deleteZones 执行list
     */
    private void deleteByUserIdAndZones(Set<Long> userIds, Collection<String> deleteZones) {
        this.userZoneMapper.deleteByUserIdAndZones(userIds, deleteZones);
    }

    /**
     * 批量新增用户分区关系
     *
     * @param doList 执行list
     */
    private void insertUserZoneBatch(List<UserZoneDo> doList) {
        Long loginUserId = WebFrameworkUtils.getLoginUserId();
        this.userZoneMapper.insertUserZoneBatch(loginUserId, LocalDateTime.now(), doList);
    }

    /**
     * 批量新增用户项目关系
     *
     * @param doList 执行list
     */
    private void insertUserProjectBatch(List<UserProjectDo> doList) {
        Long loginUserId = WebFrameworkUtils.getLoginUserId();
        this.userZoneMapper.insertUserProjectBatch(loginUserId, LocalDateTime.now(), doList);
    }

    /**
     * 批量删除用户项目关系
     *
     * @param userIds        用户id
     * @param deleteProjects 执行list
     */
    private void deleteByUserIdAndProjects(Set<Long> userIds, Collection<String> deleteProjects) {
        this.userZoneMapper.deleteByUserIdAndProjects(userIds, deleteProjects);
    }

    /**
     * 批量新增用户单位关系
     *
     * @param doList 执行list
     */
    private void insertUserDeptBatch(List<UserDeptDo> doList) {
        Long loginUserId = WebFrameworkUtils.getLoginUserId();
        this.userZoneMapper.insertUserDeptBatch(loginUserId, LocalDateTime.now(), doList);
    }

    /**
     * 批量删除用户单位关系
     *
     * @param userIds     用户id
     * @param deleteDepts 执行list
     */
    private void deleteByUserIdAndDepts(Set<Long> userIds, Collection<Long> deleteDepts) {
        this.userZoneMapper.deleteByUserIdAndDepts(userIds, deleteDepts);
    }

    private UserRolesRespVo convertUserRolesCreators(UserRolesRespVo vo, UserRespVO respVO) {
        if (respVO != null) {
            vo.setCreatorName(respVO.getNickname());
            vo.setCreatorDept(respVO.getDeptName());
        }
        return vo;
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PermissionServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

}
